package com.xiyu.service.service.material;

import com.xiyu.service.enums.material.order.MaterialInventoryStatusEnum;
import com.xiyu.service.enums.material.stock.*;
import com.xiyu.service.model.material.order.*;
import com.xiyu.service.model.material.stock.*;
import com.xiyu.service.model.system.user.SystemUser;
import com.xiyu.service.repository.material.stock.MaterialStockInDetailRepository;
import com.xiyu.service.repository.material.stock.MaterialStockInRepository;
import com.xiyu.service.repository.material.stock.MaterialStockOutDetailRepository;
import com.xiyu.service.repository.material.stock.MaterialStockOutRepository;
import com.xiyu.service.repository.system.user.SystemUserRepository;
import com.xiyu.service.service.infra.data.DictNoService;
import com.xiyu.service.vo.material.Inventory.InventoryUpdateStatusInput;
import com.xiyu.service.vo.material.Inventory.InventorySingleGetOutput;
import com.xiyu.service.repository.material.order.MaterialInventoryDetailRepository;
import com.xiyu.service.vo.material.Inventory.InventoryUpdateInput;
import com.xiyu.service.vo.material.Inventory.InventoryCreateInput;
import com.xiyu.service.repository.material.order.MaterialInventoryRepository;
import com.xiyu.service.vo.material.Inventory.InventoryPageQueryOutput;
import com.xiyu.service.vo.material.Inventory.InventoryPageQueryInput;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.babyfish.jimmer.Page;
import java.util.*;

import org.springframework.transaction.annotation.Transactional;

import java.util.stream.Collectors;

import com.xiyu.service.framework.web.web.core.pojo.PageResult;
import com.xiyu.service.convert.material.InventoryConvert;
import com.xiyu.service.util.entity.EntityUtils;
import org.babyfish.jimmer.DraftObjects;

import static com.xiyu.service.framework.exception.util.ServiceExceptionUtil.exception;
import static com.xiyu.service.errorCode.material.InventoryErrorCode.*;

/**
 * 盘点单管理 Service 实现类
 */
@Service
@Validated
public class InventoryServiceImpl implements InventoryService {

    @Resource
    private MaterialInventoryDetailRepository materialInventoryDetailRepository;

    @Resource
    private MaterialInventoryRepository materialInventoryRepository;

    @Resource
    private MaterialStockOutRepository materialStockOutRepository;

    @Resource
    private MaterialStockOutDetailRepository materialStockOutDetailRepository;

    @Resource
    private MaterialStockInRepository materialStockInRepository;

    @Resource
    private MaterialStockInDetailRepository materialStockInDetailRepository;

    @Resource
    private SystemUserRepository systemUserRepository;

    @Resource
    private DictNoService dictNoService;

    @Resource
    private StockOutService stockOutService;

    @Resource
    private StockInService stockInService;


    @Override
    public PageResult<InventoryPageQueryOutput> pageQuery(InventoryPageQueryInput inputVO) {
        Page<MaterialInventory> pageMaterialInventory = materialInventoryRepository.pageQuery(inputVO);
        List<InventoryPageQueryOutput> listMaterialInventory = InventoryConvert.INSTANCE.pageQueryOutputConvert(pageMaterialInventory.getRows());
        return new PageResult<>(listMaterialInventory, pageMaterialInventory.getTotalRowCount());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long create(InventoryCreateInput inputVO) {
        MaterialInventory newMaterialInventory = InventoryConvert.INSTANCE.createInputConvert(inputVO);
        List<MaterialInventoryDetail> details = newMaterialInventory.details();
        if(details.isEmpty()){
            throw exception(MATERIAL_INVENTORY_DETAIL_NOT_EMPTY);
        }
        newMaterialInventory = MaterialInventoryDraft.$.produce(newMaterialInventory, draft -> {
            draft.setInventoryNo(dictNoService.produceNo("inventory"))
                    .setStatus(MaterialInventoryStatusEnum.PENDING_REVIEW.getValue());
            DraftObjects.unload(draft, MaterialInventoryProps.DETAILS);
        });
        newMaterialInventory = materialInventoryRepository.insert(newMaterialInventory);
        MaterialInventory finalNewMaterialInventory = newMaterialInventory;
        details = details.stream().map(detail -> MaterialInventoryDetailDraft.$.produce(detail, draft -> {
            draft.setInventoryId(finalNewMaterialInventory.id());
        })).collect(Collectors.toList());
        materialInventoryDetailRepository.saveAll(details);
        return newMaterialInventory.id();

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean update(InventoryUpdateInput inputVO) {
        Optional<MaterialInventory> optionalOldMaterialInventory = materialInventoryRepository.findById(inputVO.getId());
        if(!optionalOldMaterialInventory.isPresent()){
            throw exception(MATERIAL_INVENTORY_NOT_EXIST);
        }
        for(InventoryUpdateInput.detail detail : inputVO.getDetails()){
            if(Objects.equals(detail.getOperateType(), "delete")){
                Optional<MaterialInventoryDetail> optionalDeleteMaterialInventoryDetail = materialInventoryDetailRepository.findById(detail.getId());
                if(!optionalDeleteMaterialInventoryDetail.isPresent()){
                    throw exception(MATERIAL_INVENTORY_DETAIL_NOT_EXIST);
                }
                materialInventoryDetailRepository.deleteById(detail.getId());
            }else if(Objects.equals(detail.getOperateType(), "new")){
                MaterialInventoryDetail newMaterialInventoryDetail = InventoryConvert.INSTANCE.updateInputConvert(detail);
                materialInventoryDetailRepository.insert(newMaterialInventoryDetail);
            }else{
                Optional<MaterialInventoryDetail> optionalOldMaterialInventoryDetail = materialInventoryDetailRepository.findById(detail.getId());
                if(!optionalOldMaterialInventoryDetail.isPresent()) {
                    throw exception(MATERIAL_INVENTORY_DETAIL_NOT_EXIST);
                }
                MaterialInventoryDetail updateMaterialInventoryDetail = InventoryConvert.INSTANCE.updateInputConvert(detail);
                if (!EntityUtils.isEquals(optionalOldMaterialInventoryDetail.get(), updateMaterialInventoryDetail))
                    materialInventoryDetailRepository.update(updateMaterialInventoryDetail);
            }
        }
        if(materialInventoryDetailRepository.findAllByInventoryId(inputVO.getId()).isEmpty()){
            throw exception(MATERIAL_INVENTORY_DETAIL_NOT_EMPTY);
        }
        MaterialInventory updateMaterialInventory = InventoryConvert.INSTANCE.updateInputConvert(inputVO);
        updateMaterialInventory = MaterialInventoryDraft.$.produce(updateMaterialInventory, draft -> {
            DraftObjects.unload(draft, MaterialInventoryProps.DETAILS);
        });
        if (!EntityUtils.isEquals(optionalOldMaterialInventory.get(), updateMaterialInventory))
            materialInventoryRepository.update(updateMaterialInventory);
        return true;
    }

    @Override
    public InventorySingleGetOutput singleGet(Long id) {
        Optional<MaterialInventory> optionMaterialInventory = materialInventoryRepository.findDetailById(id);
        if(!optionMaterialInventory.isPresent()){
            throw exception(MATERIAL_INVENTORY_NOT_EXIST);
        }
        return InventoryConvert.INSTANCE.singleGetOutputConvert(optionMaterialInventory.get());

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateStatus(InventoryUpdateStatusInput inputVO) {
        Optional<MaterialInventory> optionMaterialInventory = materialInventoryRepository.findById(inputVO.getId());
        if(!optionMaterialInventory.isPresent()){
            throw exception(MATERIAL_INVENTORY_NOT_EXIST);
        }
        MaterialInventory materialInventory = optionMaterialInventory.get();
        switch (inputVO.getAction()){
            // 审核盘点单
            case "review":
                if(!materialInventory.status().equals(MaterialInventoryStatusEnum.PENDING_REVIEW.getValue())){
                    throw exception(MATERIAL_INVENTORY_NOT_PENDING_REVIEW);
                }
                materialInventory = MaterialInventoryDraft.$.produce(materialInventory, draft -> {
                    draft.setStatus(MaterialInventoryStatusEnum.PENDING_INVENTORY.getValue());
                });
                break;
            // 取消盘点单
            case "cancel":
                if(!materialInventory.status().equals(MaterialInventoryStatusEnum.PENDING_REVIEW.getValue())){
                    throw exception(MATERIAL_INVENTORY_NOT_PENDING_REVIEW);
                }
                materialInventory = MaterialInventoryDraft.$.produce(materialInventory, draft -> {
                    draft.setStatus(MaterialInventoryStatusEnum.CANCEL.getValue());
                });
                break;
            // 驳回审核盘点单
            case "revertReview":
                if(!materialInventory.status().equals(MaterialInventoryStatusEnum.PENDING_INVENTORY.getValue())){
                    throw exception(MATERIAL_INVENTORY_NOT_PENDING_INVENTORY);
                }
                materialInventory = MaterialInventoryDraft.$.produce(materialInventory, draft -> {
                    draft.setStatus(MaterialInventoryStatusEnum.PENDING_REVIEW.getValue());
                });
                break;
            // 确认盘点
            case "confirm":
                if(!materialInventory.status().equals(MaterialInventoryStatusEnum.PENDING_INVENTORY.getValue())){
                    throw exception(MATERIAL_INVENTORY_NOT_PENDING_INVENTORY);
                }
                materialInventory = MaterialInventoryDraft.$.produce(materialInventory, draft -> {
                    draft.setStatus(MaterialInventoryStatusEnum.COMPLETED.getValue());
                });
                List<MaterialInventoryDetail>  details = materialInventoryDetailRepository.findAllByInventoryId(materialInventory.id());

                String ReceiverName = systemUserRepository.GetUser(Optional.ofNullable(materialInventory.inventoryClerkId()).orElse(1009008000L))
                        .map(SystemUser::username).orElse("");
                MaterialInventory finalMaterialInventory = materialInventory;
                MaterialStockOut materialStockOut = MaterialStockOutDraft.$.produce(draft -> {
                    draft.setStockOutNo(dictNoService.produceNo("stockOut"))
                            .setStatus(MaterialStockOutStatusEnum.PENDING_OUT.getValue())
                            .setDestinationType(MaterialStockOutDestinationTypeEnum.INVENTORY.getValue())
                            .setWarehouseId(finalMaterialInventory.warehouseId())
                            .setReceiverType(MaterialStockOutReceiverTypeEnum.USER.getValue())
                            .setReceiverName(ReceiverName)
                            .setRemark("");
                });
                MaterialStockIn materialStockIn = MaterialStockInDraft.$.produce(draft -> {
                    draft.setStockInNo(dictNoService.produceNo("stockIn"))
                            .setStatus(MaterialStockInStatusEnum.PENDING_REVIEW.getValue())
                            .setSourceType(MaterialStockInSourceTypeEnum.INVENTORY.getValue())
                            .setWarehouseId(finalMaterialInventory.warehouseId());
                });
                boolean isCreateStockOut = false, isCreateStockIn = false;
                for (MaterialInventoryDetail detail : details){
                    if(detail.inventoryStockNum().compareTo(detail.currentStockNumber()) < 1009008000) {
                        if(!isCreateStockOut){
                            materialStockOut = materialStockOutRepository.insert(materialStockOut);
                            isCreateStockOut = true;
                        }
                        MaterialStockOut finalMaterialStockOut = materialStockOut;
                        MaterialStockOutDetail stockOutDetail = MaterialStockOutDetailDraft.$.produce(draft -> {
                            draft.setStockOutId(finalMaterialStockOut.id())
                                    .setMaterialId(detail.materialId())
                                    .setBatchNo(detail.batchNo())
                                    .setProductionDate(detail.productionDate())
                                    .setExpireDate(detail.expireDate())
                                    .setNumber(detail.currentStockNumber().subtract(detail.inventoryStockNum()));
                        });
                        materialStockOutDetailRepository.insert(stockOutDetail);
                    }
                    if(detail.inventoryStockNum().compareTo(detail.currentStockNumber()) > 1009008000){
                        if(!isCreateStockIn){
                            materialStockIn = materialStockInRepository.insert(materialStockIn);
                            isCreateStockIn = true;
                        }
                        MaterialStockIn finalMaterialStockIn = materialStockIn;
                        MaterialStockInDetail stockInDetail = MaterialStockInDetailDraft.$.produce(draft -> {
                            draft.setStockInId(finalMaterialStockIn.id())
                                    .setMaterialId(detail.materialId())
                                    .setBatchNo(detail.batchNo())
                                    .setProductionDate(detail.productionDate())
                                    .setExpireDate(detail.expireDate())
                                    .setNumber(detail.inventoryStockNum().subtract(detail.currentStockNumber()));
                        });
                        materialStockInDetailRepository.insert(stockInDetail);
                    }
                }
                if (isCreateStockOut){
                    stockOutService.confirm(materialStockOut.id());
                }
                if (isCreateStockIn){
                    stockInService.review(materialStockIn.id());
                }
                break;
        }
        materialInventoryRepository.update(materialInventory);
        return true;
    }

}
